/*
 * Copyright (c) 2010, Brookhaven National Laboratory
 * All rights reserved.
 * The software in this package is published under the terms
 * of the BSD license a copy of which has been included with
 * this distribution in the license.txt file.
 */


package org.oxtypes.protobuf;

import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;

import org.omg.dds.type.dynamic.DynamicType;
import org.oxtypes.DynamicData;

import com.google.protobuf.ByteString;
import com.google.protobuf.WireFormat;
import com.google.protobuf.WireFormat.*;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.Descriptors.*;

// OxTypes

import com.google.protobuf.PublicWireFormat;

import org.oxtypes.DynamicDataFactory;

import org.oxtypes.protobuf.impl.PackedFieldBuilder;
import org.oxtypes.protobuf.impl.RepeatedFieldBuilders;
import org.oxtypes.protobuf.impl.RepeatedFieldBuilders.*;

/**
 *
 */
public class DynamicDataBuilder {

    public DynamicDataBuilder(){
        repeatedBuilders = new RepeatedFieldBuilders();
        packedBuilder = new PackedFieldBuilder();
    }

    public DynamicData build(DynamicType dt, byte[] bytes) throws IOException {

        CodedInputStream input = CodedInputStream.newInstance(bytes);

        DynamicDataFactory ddf = DynamicDataFactory.getInstance();
        DynamicData dd = ddf.createData(dt);

        if(dd == null){
            throw new IllegalArgumentException(
                    "can not create dynamic data for the specified type");
        }

        readMessage(input, dd, 0);
        return dd;
    }

    private void readMessage(CodedInputStream input, DynamicData dd, int endTag)
        throws IOException {

        DynamicTypeRegistry dtr = DynamicTypeRegistry.getInstance();

        DynamicType dt = dd.getType();
        Descriptor d = dtr.getDescriptor(dt);

        Map<Integer, RepeatedFieldBuilder> repBuilders =
                new TreeMap<Integer, RepeatedFieldBuilder>();

        while (true) {

            int tag = input.readTag();
            if (tag == endTag) {
                break;
            }

            int id = WireFormat.getTagFieldNumber(tag);
            FieldDescriptor fd = d.findFieldByNumber(id);

            if(!fd.isRepeated()) readElementNoTag(input, id, fd, dd);
            else if(fd.isPacked()) packedBuilder.readFieldNoTag(input, id, fd, dd);
            else {
                RepeatedFieldBuilder repBuilder = repBuilders.get(id);
                if(repBuilder == null) {
                    repBuilder = repeatedBuilders.createBuilder(fd.getLiteType());
                    repBuilders.put(id, repBuilder);
                }
                repBuilder.readFieldNoTag(input);
            }
        }

        System.out.println("set repeated fields ...");

        for(Map.Entry<Integer, RepeatedFieldBuilder> entry: repBuilders.entrySet()){
            entry.getValue().setDynmaicData(entry.getKey(), dd);
        }
        
        repBuilders.clear();
    }

    private void readElementNoTag(CodedInputStream input, int id,
          FieldDescriptor fd, DynamicData dd) throws IOException {
  
        switch(fd.getLiteType()){
            case DOUBLE  : dd.setFloat64Value(id, input.readDouble()); break;
            case FLOAT   : dd.setFloat32Value(id, input.readFloat()); break;
            case INT64   : dd.setInt64Value(id, input.readInt64()); break;
            case INT32   : dd.setInt32Value(id, input.readInt32()); break;
            case UINT64  : dd.setInt64Value(id, input.readUInt64()); break;
            case UINT32  : dd.setInt32Value(id, input.readUInt32()); break;
            case SINT64  : dd.setInt64Value(id, input.readSInt64()); break;
            case SINT32  : dd.setInt32Value(id, input.readSInt32()); break;
            case FIXED64 : dd.setInt64Value(id, input.readFixed64()); break;
            case FIXED32 : dd.setInt32Value(id, input.readFixed32()); break;
            case SFIXED64: dd.setInt64Value(id, input.readSFixed64()); break;
            case SFIXED32: dd.setInt32Value(id, input.readSFixed32()); break;
            case BOOL    : dd.setBooleanValue(id, input.readBool()); break;
            case ENUM    : dd.setInt32Value(id, input.readEnum()); break;
            case STRING  : dd.setStringValue(id, input.readString()); break;
            case BYTES   : readBytesNoTag(input, id, dd); break;
            case GROUP   : readEmbeddedGroup(input, id, dd); break;
            case MESSAGE : readEmbeddedMessage(input, id, dd); break;
        }
    }

    private void readBytesNoTag(CodedInputStream input, 
            int id, DynamicData dd) throws IOException {

          ByteString bytes = input.readBytes();
          dd.setByteValues(id, bytes.toByteArray(), 0, bytes.size());
    }

    private void readEmbeddedMessage(CodedInputStream input,
            int id, DynamicData dd) throws IOException {

          int byteSize = input.readRawVarint32();
          int oldLimit = input.pushLimit(byteSize);

          readMessage(input, dd.getComplexValue(id), 0);
 
          input.popLimit(oldLimit);
    }

    private void readEmbeddedGroup(CodedInputStream input,
          int id, DynamicData dd) throws IOException {

          int endTag = PublicWireFormat.makeTag(id, PublicWireFormat.WIRETYPE_END_GROUP);
          readMessage(input, dd.getComplexValue(id), endTag);
    }

    private RepeatedFieldBuilders repeatedBuilders;
    private PackedFieldBuilder packedBuilder;

}
